<!DOCTYPE html>
<html>
  <head>
      <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta content="yes" name="apple-mobile-web-app-capable" />
  <meta content="black" name="apple-mobile-web-app-status-bar-style" />
  <meta name="referrer" content="never">
  <meta name="keywords" content="">
  <meta name="description" content="">
  <meta name="author" content="kveln">
  <title>k8s使用的web框架：go-restful 源码分析 | John Wong&#39;s Blog</title>
  <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
  <!-- <link href="https://blog.jwangkun.com/media/css/bootstrap.min.css" rel="stylesheet"> -->
  <!--  <link href="https://blog.jwangkun.com/media/css/all.min.css" rel="stylesheet" type="text/css"> -->
  <link href="https://cdn.bootcss.com/font-awesome/5.11.2/css/all.min.css" rel="stylesheet">
  <link href='https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
  <link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
  <link rel="alternate" type="application/rss+xml" title="k8s使用的web框架：go-restful 源码分析 | John Wong&#39;s Blog » Feed" href="https://blog.jwangkun.com/atom.xml">
  <link rel="stylesheet"href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/androidstudio.min.css">
  <link href="https://blog.jwangkun.com/styles/main.css" rel="stylesheet">
  <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/highlight.min.js"></script>
  <!-- <script src="https://blog.jwangkun.com/media/scripts/jquery.min.js"></script> -->
  <script>hljs.initHighlightingOnLoad();</script>
  

    <meta property="og:description" content="k8s使用的web框架：go-restful 源码分析"/>
    <meta property="og:url" content="https://blog.jwangkun.com/post/cjiYeW8zk/"/>
    <meta property="og:locale" content="zh-CN"/>
    <meta property="og:type" content="website"/>
    <meta property="og:site_name" content="John Wong&#39;s Blog"/>
  </head>
  <body>
  	<!-- Navigation -->
  <nav class="navbar navbar-expand-lg navbar-light fixed-top" id="mainNav">
    <div class="container">
      <a class="navbar-brand" href="https://blog.jwangkun.com">John Wong&#39;s Blog</a>
      <button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
        Menu
        <i class="fas fa-bars"></i>
      </button>
      <div class="collapse navbar-collapse" id="navbarResponsive">
        <ul class="navbar-nav ml-auto">
          
          <li class="nav-item">
              
              <a class="nav-link" href="/">首页</a>
              
          </li>
          
          <li class="nav-item">
              
              <a class="nav-link" href="/archives">归档</a>
              
          </li>
          
          <li class="nav-item">
              
              <a class="nav-link" href="/tags">标签</a>
              
          </li>
          
          <li class="nav-item">
              
              <a class="nav-link" href="/post/about">关于</a>
              
          </li>
          
        </ul>
      </div>
    </div>
  </nav>
  <!-- Page Header -->
  <header class="masthead" style="background-image: url('https://blog.jwangkun.com/media/images/home-bg.jpg')">
    <div class="overlay"></div>
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-md-10 mx-auto">
          <div class="post-heading">
          	<span class="tags">
          	 
            <a href="https://blog.jwangkun.com/tag/GjQjs7vC0/" class="tag">Go</a>
            
            <a href="https://blog.jwangkun.com/tag/GwW0BvBxfA/" class="tag">源码</a>
            
            <a href="https://blog.jwangkun.com/tag/efVGHoi38/" class="tag">Kubernetes</a>
            
        </span>
            <h1>k8s使用的web框架：go-restful 源码分析</h1>
            <span class="meta">
            	Posted on
              2020-10-16，16 min read
            </span>
          </div>
        </div>
      </div>
    </div>
  </header>

  <!-- Post Content -->
  <article>
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-md-10 mx-auto">
          <h2 id="概述">概述</h2>
<p><a href="https://github.com/emicklei/go-restful">go-restful</a>是一个用go语言开发的快速构建restful风格的web框架。k8s最核心的组件kube-apiserver使用到了该框架，该框架的代码比较精简，这里做个简单的功能介绍，然后分析相关源码。</p>
<p>go-restful基于golang官方的net/http实现，在深入学习之前，建议先看一下本人之前整理的关于<a href="https://juejin.im/post/6873499014482886663">官方http源码分析</a>的文章</p>
<p>go-restful定义了三个重要的数据结构：</p>
<ul>
<li>Router：表示一条路由，包含url、回调处理函数</li>
<li>Webservice：表示一个服务</li>
<li>Container：表示一个服务器</li>
</ul>
<p>三者的关系如下：</p>
<ul>
<li>go-restful支持多个container，一个container相当于一个http server，不同的container监控不同的地址和端口</li>
<li>每个container可以包含多个webservice，相当于一组不同服务的分类</li>
<li>每个webservice包含多个Router（路由），Router根据http请求的URL路由到对应的处理函数（Handler Func）</li>
</ul>
<p><a href="https://www.processon.com/view/link/5f634b100791295dccc43322">高清地址</a> <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa81b0721da44c10b059d3ef9f149aa2~tplv-k3u1fbpfcp-zoom-1.image" alt="go-restful核心数据结构" loading="lazy"></p>
<h2 id="快速上手">快速上手</h2>
<p>引入包：</p>
<pre><code class="language-bash">go get github.com/emicklei/go-restful/v3

</code></pre>
<p>hello-world 代码示例如下，启动后访问<code>loclahost:8080/hello</code>可以看到页面响应 world</p>
<pre><code class="language-Go">package main

import (
  &quot;github.com/emicklei/go-restful/v3&quot;
  &quot;io&quot;
  &quot;log&quot;
  &quot;net/http&quot;
)

func main() {
  // 创建WebService
  ws := new(restful.WebService)
  // 为WebService设置路由和回调函数
  ws.Route(ws.GET(&quot;/hello&quot;).To(hello))
  // 将WebService添加到默认生成的Container中
  // 默认生成的container的代码在web_service_container.go的init方法中
  restful.Add(ws)
  // 启动服务
  log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
}

// 路由对应的回调函数
func hello(req *restful.Request, resp *restful.Response) {
  io.WriteString(resp, &quot;world&quot;)
}

</code></pre>
<h2 id="源码分析">源码分析</h2>
<p>看到前面的快速上手代码，你有没有这样的疑惑：创建完成WebServie之后，添加到默认的Container，也没有把container传给谁，只是启动了服务监听就能自动识别到container呢？</p>
<p>想要揭开答案，让我们一起分析下源码吧。在这之前，还是建议先看下本人之前整理的关于<a href="https://juejin.im/post/6873499014482886663">官方http源码分析</a>的文章，因为go-restful会基于官方提供的http包去实现功能</p>
<p>下图是整理的源码核心逻辑图。</p>
<p><a href="https://www.processon.com/view/link/5f6348e35653bb28eb449c5c">高清地址</a></p>
<figure data-type="image" tabindex="1"><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/488077841e224b1d8931de0b60fce992~tplv-k3u1fbpfcp-zoom-1.image" alt="go-restful核心源码逻辑" loading="lazy"></figure>
<h3 id="核心数据结构">核心数据结构</h3>
<h4 id="route">Route</h4>
<p>前面提到过Route是go-restful的三个概念之一是路由，内部的数据结构是Route，先看一下源码。</p>
<p>源码位置：github.com/emicklei/go-restful/router.go</p>
<pre><code class="language-Go">type Route struct {
  Method   string
  Produces []string
  Consumes []string
  // 请求的路径：root path + described path
  Path     string
  // handler处理函数
  Function RouteFunction
  // 拦截器
  Filters  []FilterFunction
  If       []RouteSelectionConditionFunction

  // cached values for dispatching
  relativePath string
  pathParts    []string
  pathExpr     *pathExpression // cached compilation of relativePath as RegExp

  // documentation
  Doc                     string
  Notes                   string
  Operation               string
  ParameterDocs           []*Parameter
  ResponseErrors          map[int]ResponseError
  DefaultResponse         *ResponseError
  ReadSample, WriteSample interface{} // structs that model an example request or response payload

  // Extra information used to store custom information about the route.
  Metadata map[string]interface{}

  // marks a route as deprecated
  Deprecated bool

  //Overrides the container.contentEncodingEnabled
  contentEncodingEnabled *bool
}

</code></pre>
<h4 id="routebuilder">RouteBuilder</h4>
<p>RouteBuilder用于构造Route信息，根据名字就知道使用了建造者模式</p>
<p>源码位置：github.com/emicklei/go-restful/router.go</p>
<pre><code class="language-Go">// 大部分属性和Route一样
type RouteBuilder struct {
  rootPath    string
  currentPath string
  produces    []string
  consumes    []string
  httpMethod  string        // required
  function    RouteFunction // required
  filters     []FilterFunction
  conditions  []RouteSelectionConditionFunction

  typeNameHandleFunc TypeNameHandleFunction // required
  ...
}

</code></pre>
<h4 id="webservice">Webservice</h4>
<p>WebService拥有一组Route，这组Router有公共的rootPath，在逻辑上将具有相同前置的路由请求放到一起</p>
<p>源码位置：github.com/emicklei/go-restful/web_service.go</p>
<pre><code class="language-Go">type WebService struct {
  // Webservice中的Route共享一个rootPath
  rootPath       string
  pathExpr       *pathExpression // cached compilation of rootPath as RegExp
  routes         []Route
  produces       []string
  consumes       []string
  pathParameters []*Parameter
  filters        []FilterFunction
  documentation  string
  apiVersion     string

  typeNameHandleFunc TypeNameHandleFunction

  dynamicRoutes bool

  // 保护路由，防止多线程写操作出现并发问题
  routesLock sync.RWMutex
}

</code></pre>
<h4 id="container">Container</h4>
<p>一个Container包含多个Service，不同的Container监听不同的ip地址或端口，他们之间提供的服务的独立的。</p>
<p>源码位置：github.com/emicklei/go-restful/container.go</p>
<pre><code class="language-Go">type Container struct {
  webServicesLock        sync.RWMutex
  // Container内部有多个webservice
  webServices            []*WebService
  ServeMux               *http.ServeMux
  isRegisteredOnRoot     bool
  containerFilters       []FilterFunction
  doNotRecover           bool // default is true
  recoverHandleFunc      RecoverHandleFunction
  serviceErrorHandleFunc ServiceErrorHandleFunction
  router                 RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
  contentEncodingEnabled bool          // default is false
}

</code></pre>
<h3 id="核心代码流程梳理">核心代码流程梳理</h3>
<p>从前面demo中的代码开始入手，分析整个调用流程。</p>
<p>整体流程包括：</p>
<ul>
<li>创建WebService对象</li>
<li>为WebService对象添加路由地址和处理函数</li>
<li>将WebService添加到Container中（这里没有声明Containerr，用的默认Container）</li>
<li>启动服务监听端口，等待服务请求</li>
</ul>
<pre><code class="language-Go">func main() {
  ws := new(restful.WebService)
  ws.Route(ws.GET(&quot;/hello&quot;).To(hello))
  restful.Add(ws)
  log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
}

</code></pre>
<h4 id="webservice添加路由">WebService添加路由</h4>
<p>主要分析<code>ws.Route(ws.GET(&quot;/hello&quot;).To(hello))</code></p>
<h5 id="构造routebuilder对象">构造RouteBuilder对象</h5>
<pre><code class="language-Go">// Get方法内部new了一个RouteBuilder，用于构造Route对象
func (w *WebService) GET(subPath string) *RouteBuilder {
  // 典型的建造者模式用法
  return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method(&quot;GET&quot;).Path(subPath)
}

// 建造者模式：给属性赋值
// 其他的方法类似，就不再展开
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
  b.typeNameHandleFunc = handler
  return b
}

// Get方法后，属性并没有完全构造完，handler处理函数是用单独的To方法赋值的
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
  b.function = function
  return b
}

</code></pre>
<h5 id="根据routebuilder生成route对象">根据RouteBuilder生成Route对象</h5>
<pre><code class="language-Go">func (w *WebService) Route(builder *RouteBuilder) *WebService {
  w.routesLock.Lock()
  defer w.routesLock.Unlock()
  // 填充默认值
  builder.copyDefaults(w.produces, w.consumes)
  // 调用RouteBuilder的Build方法，构造Route
  // 并将Route添加到routes列表中
  w.routes = append(w.routes, builder.Build())
  return w
}

// Build方法返回Route对象
func (b *RouteBuilder) Build() Route {
  ...
  route := Route{
    ...
  }
  route.postBuild()
  return route
}

</code></pre>
<h3 id="webservice添加到container">WebService添加到Container</h3>
<p>主要分析<code>restful.Add(ws)</code>。这里要特别注意的是：</p>
<ul>
<li>将http的DefaultServeMux传给了DefaultServeMux的ServeMux</li>
<li>调用Golang官方http包中的ServeMux.HandleFunc函数处理请求</li>
<li>处理函数统一为c.dispatch，dispatch再在内部做路由分发</li>
</ul>
<p>源码位置：github.com/emicklei/go-restful/web_service_container.go</p>
<pre><code class="language-Go">// 定义全局变量,作为默认的Container
var DefaultContainer *Container

// init函数在别的包import时，自动触发。也就是只要引用了go-restful框架，就会默认有一个Container
func init() {
  DefaultContainer = NewContainer()
  // 这里将Golang中标准http包下的默认路由对象DefaultServeMux赋值给Container的ServeMux
  // 这里要特别注意，正是因为这个地方的逻辑，就能回答前面我们提出的问题。go-restful和http库，通过这个赋值建立了关联关系。
  DefaultContainer.ServeMux = http.DefaultServeMux
}

// 生成默认的container
func NewContainer() *Container {
  return &amp;Container{
    webServices:            []*WebService{},
    ServeMux:               http.NewServeMux(),
    isRegisteredOnRoot:     false,
    containerFilters:       []FilterFunction{},
    doNotRecover:           true,
    recoverHandleFunc:      logStackOnRecover,
    serviceErrorHandleFunc: writeServiceError,
    // 默认的路由选择器用的是CurlyRouter
    router:                 CurlyRouter{},
    contentEncodingEnabled: false}
}

// 将WebService添加到默认Container中
func Add(service *WebService) {
  DefaultContainer.Add(service)
}

// Add
func (c *Container) Add(service *WebService) *Container {
  ...
  // if rootPath was not set then lazy initialize it
  if len(service.rootPath) == 0 {
    service.Path(&quot;/&quot;)
  }

  // 判断有没有重复的RootPath，不同的WebService，rootPath不能重复
  for _, each := range c.webServices {
    if each.RootPath() == service.RootPath() {
      log.Printf(&quot;WebService with duplicate root path detected:['%v']&quot;, each)
      os.Exit(1)
    }
  }

  if !c.isRegisteredOnRoot {
    // 核心逻辑：为servcie添加handler处理函数
    // 这里将c.ServeMux作为参数传入，这个值是前面提到的http.DefaultServeMux
    c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
  }
  // 将webServices添加到container的webservice列表中
  c.webServices = append(c.webServices, service)
  return c
}

// addHandler
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
  pattern := fixedPrefixPath(service.RootPath())
  ...
  // 这里的关键函数：serveMux.HandleFunc，是Golang标准包中实现路由的函数
  // go-restful中将路由处理函数统一交给c.dispatch函数，可以看出整个go-restful框架中，最核心的就是这个函数了
  if !alreadyMapped {
    serveMux.HandleFunc(pattern, c.dispatch)
    if !strings.HasSuffix(pattern, &quot;/&quot;) {
      serveMux.HandleFunc(pattern+&quot;/&quot;, c.dispatch)
    }
  }
  return false
}

</code></pre>
<h4 id="路由分发函数dispatch">路由分发函数dispatch</h4>
<p>如何由container -&gt; webservice -&gt; handler 实现层级分发？ go-restful框架通过<code>serveMux.HandleFunc(pattern, c.dispatch)</code>函数，一边连接了Golang提供的官方http扩展机制，另一边通过一个dispatch实现了路由的分发，这样就不用单独写很多的handler了。</p>
<p>这个函数的核心是<code>c.router.SelectRoute</code>，根据请求找到合适的webservice和route</p>
<p>源码位置：github.com/emicklei/go-restful/container.go</p>
<pre><code class="language-Go">func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
  ...
  // 根据请求，找到最合适的webService和route
  // 这个方法后面单独介绍
  func() {
    ...
    webService, route, err = c.router.SelectRoute(
      c.webServices,
      httpRequest)
  }()
  ...
  if err != nil {
    // 构造过滤器
    chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
      switch err.(type) {
      case ServiceError:
        ser := err.(ServiceError)
        c.serviceErrorHandleFunc(ser, req, resp)
      }
      // TODO
    }}
    // 运行Container的过滤器
    chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
    return
  }

  // 尝试将router对象转为PathProcessor对象
  // 我们使用的是默认的Container，前面介绍过router默认用的CurlyRouter，
  // SelectRoute的其中一个实现类RouterJSR311，也实现了PathProcessor。所以如果用了RouterJSR311,这里接口转换才能获取到值
  // 而默认的CurlyRouter并没有实现PathProcessor接口，因此这里转换后是空值，会走到下一个if语句
  pathProcessor, routerProcessesPath := c.router.(PathProcessor)
  if !routerProcessesPath {
    // 使用默认的路处理器
    pathProcessor = defaultPathProcessor{}
  }
  // 从request的url请求中抽取参数
  pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
  wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
  // 如果有filter的话，处理将所有的filter添加到filter链中
  if size := len(c.containerFilters) + len(webService.filters) + len(route.Filters); size &gt; 0 {
    // compose filter chain
    allFilters := make([]FilterFunction, 0, size)
    allFilters = append(allFilters, c.containerFilters...)
    allFilters = append(allFilters, webService.filters...)
    allFilters = append(allFilters, route.Filters...)
    chain := FilterChain{Filters: allFilters, Target: route.Function}
    chain.ProcessFilter(wrappedRequest, wrappedResponse)
  } else {
    // no filters, handle request by route
    // 没有filter，通过route直接处理请求
    route.Function(wrappedRequest, wrappedResponse)
  }
}

</code></pre>
<h4 id="路由选择">路由选择</h4>
<p>前面的dispatch中提到的<code>c.router.SelectRoute</code>的作用是选择合适的webservice和route，这里专门介绍一下。</p>
<p>container中的router属性是一个<code>RouteSelector</code>接口</p>
<pre><code class="language-Go">type RouteSelector interface {
  // SelectRoute根据输入的http请求和webservice列表，找到一个路由并返回
  SelectRoute(
    webServices []*WebService,
    httpRequest *http.Request) (selectedService *WebService, selected *Route, err error)
}

</code></pre>
<p>go-restful框架中共有两个实现类：</p>
<ul>
<li>CurlyRouter</li>
<li>RouterJSR311</li>
</ul>
<p>前面分析代码知道CurlyRouter是默认实现，所以这里我们主要分析CurlyRouter的SelectRoute函数</p>
<pre><code class="language-Go">// 选择路由功能
func (c CurlyRouter) SelectRoute(
  webServices []*WebService,
  httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
  // 解析url，根据'/'拆分为token列表
  requestTokens := tokenizePath(httpRequest.URL.Path)
  // 根据tokens列表和webservice的路由表做匹配，返回一个最合适的webservice
  detectedService := c.detectWebService(requestTokens, webServices)
  ...
  // 返回webservice中匹配的routes集合
  candidateRoutes := c.selectRoutes(detectedService, requestTokens)
  ...
  // 从前面的list中找到最合适的route
  selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
  if selectedRoute == nil {
    return detectedService, nil, err
  }
  return detectedService, selectedRoute, nil
}

// 选择webservice
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
  var best *WebService
  score := -1
  for _, each := range webServices {
    // 计算webservice的得分
    matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
    // 返回得分最高的webservice
    if matches &amp;&amp; (eachScore &gt; score) {
      best = each
      score = eachScore
    }
  }
  // 将得分最高的webservice返回
  return best
}

// 计算webservice得分
func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
  if len(tokens) &gt; len(requestTokens) {
    return false, 0
  }
  score := 0
  for i := 0; i &lt; len(tokens); i++ {
    each := requestTokens[i]
    other := tokens[i]
    if len(each) == 0 &amp;&amp; len(other) == 0 {
      score++
      continue
    }
    if len(other) &gt; 0 &amp;&amp; strings.HasPrefix(other, &quot;{&quot;) {
      // no empty match
      if len(each) == 0 {
        return false, score
      }
      score += 1
    } else {
      // not a parameter
      if each != other {
        return false, score
      }
      score += (len(tokens) - i) * 10 //fuzzy
    }
  }
  return true, score
}

// 初选：匹配path，返回一批Route作为备选
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
  // 选中的Route存放到sortableCurlyRoutes中
  candidates := make(sortableCurlyRoutes, 0, 8)
  // 遍历webservice下所有的route
  for _, each := range ws.routes {
    // paramCount：正则命中
    // staticCount：完全匹配命中
    matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb)
    // 如果匹配，加入到备选列表中
    if matches {
      candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
    }
  }
  // 排序备选的route
  sort.Sort(candidates)
  return candidates
}

// 二次筛选：匹配属性等信息。返回最合适的Route
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
  // tracing is done inside detectRoute
  return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
}

// 匹配多个属性是否匹配：method、content-type、accept
func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
  candidates := make([]*Route, 0, 8)
  for i, each := range routes {
    ok := true
    for _, fn := range each.If {
      if !fn(httpRequest) {
        ok = false
        break
      }
    }
    if ok {
      candidates = append(candidates, &amp;routes[i])
    }
  }
  if len(candidates) == 0 {
    if trace {
      traceLogger.Printf(&quot;no Route found (from %d) that passes conditional checks&quot;, len(routes))
    }
    return nil, NewError(http.StatusNotFound, &quot;404: Not Found&quot;)
  }

  // 判断 http method 是否匹配
  previous := candidates
  candidates = candidates[:0]
  for _, each := range previous {
    if httpRequest.Method == each.Method {
      candidates = append(candidates, each)
    }
  }
  if len(candidates) == 0 {
    if trace {
      traceLogger.Printf(&quot;no Route found (in %d routes) that matches HTTP method %s\n&quot;, len(previous), httpRequest.Method)
    }
    allowed := []string{}
  allowedLoop:
    for _, candidate := range previous {
      for _, method := range allowed {
        if method == candidate.Method {
          continue allowedLoop
        }
      }
      allowed = append(allowed, candidate.Method)
    }
    header := http.Header{&quot;Allow&quot;: []string{strings.Join(allowed, &quot;, &quot;)}}
    return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, &quot;405: Method Not Allowed&quot;, header)
  }

  // 判断 Content-Type 是否匹配
  contentType := httpRequest.Header.Get(HEADER_ContentType)
  previous = candidates
  candidates = candidates[:0]
  for _, each := range previous {
    if each.matchesContentType(contentType) {
      candidates = append(candidates, each)
    }
  }
  if len(candidates) == 0 {
    if trace {
      traceLogger.Printf(&quot;no Route found (from %d) that matches HTTP Content-Type: %s\n&quot;, len(previous), contentType)
    }
    if httpRequest.ContentLength &gt; 0 {
      return nil, NewError(http.StatusUnsupportedMediaType, &quot;415: Unsupported Media Type&quot;)
    }
  }

  // 判断 accept 是否匹配
  previous = candidates
  candidates = candidates[:0]
  accept := httpRequest.Header.Get(HEADER_Accept)
  if len(accept) == 0 {
    accept = &quot;*/*&quot;
  }
  for _, each := range previous {
    if each.matchesAccept(accept) {
      candidates = append(candidates, each)
    }
  }
  if len(candidates) == 0 {
    if trace {
      traceLogger.Printf(&quot;no Route found (from %d) that matches HTTP Accept: %s\n&quot;, len(previous), accept)
    }
    available := []string{}
    for _, candidate := range previous {
      available = append(available, candidate.Produces...)
    }
    return nil, NewError(
      http.StatusNotAcceptable,
      fmt.Sprintf(&quot;406: Not Acceptable\n\nAvailable representations: %s&quot;, strings.Join(available, &quot;, &quot;)),
    )
  }
  // 如果有多个匹配，返回第一个
  return candidates[0], nil
}

</code></pre>
<h3 id="启动服务">启动服务</h3>
<p>前面介绍过，go-restful直接操作的golang标准库http的路由对象http.DefaultServeMux，所以服务启动这一步只需要调用http标准的服务启动即可，不需要做额外的处理。即<code>http.ListenAndServe(&quot;:8080&quot;, nil)</code></p>
<h2 id="总结">总结</h2>
<p>go-restful并不是一个热度很高的golang web框架，但是k8s中用到了它，本篇文章通过源码分析对go-restful的内部实现做了简单的分析。从分析的过程来看，确实是一个精悍小巧的框架。内部更深入的功能我们没有继续研究了，只要达到能看懂k8s kube-apiserver组件源码的目的就行。</p>
<p>内部核心实现只要是：</p>
<ul>
<li>通过http包默认的路由对象DefaultServeMux添加处理函数dispatch</li>
<li>路由分发功能全部转给dispatch</li>
<li>dispatch内部调用RouteSelector的默认实现类CurlyRouter的SelectRoute方法选择合适的Route</li>
<li>调用Route中注册的handler方法，处理请求</li>
</ul>
<p>原文链接：https://juejin.im/post/6873836283509735432</p>

          
          <p class="next-post">下一篇：
            <a href="https://blog.jwangkun.com/post/AqWTrwEdK/">
              <span class="post-title">
                KubeSphere DevOps 3.0 流水线运维指南&rarr;
              </span>
            </a>
          </p>
        
        <div class="comment">
          
        </div>
      </div>
    </div>
  </article>
 <!-- Footer -->
  <footer>
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-md-10 mx-auto">
          <ul class="list-inline text-center">
            
            
              
            
              
            
              
            
              
            
              
            
              
            
              
              <li class="list-inline-item">
              <a href="https://blog.jwangkun.com/atom.xml" target="_blank">
                <span class="fa-stack fa-lg">
                  <i class="fas fa-circle fa-stack-2x"></i>
                  <i class="fas fa-rss fa-stack-1x fa-inverse"></i>
                </span>
              </a>
              </li>
          </ul>
          <p class="copyright text-muted">Copyright &copy;<span>John Wong&#39;s Blog</span><br><a href="https://github.com/getgridea/gridea" class="Themeinfo">Powered by Gridea</a></p>
        </div>
      </div>
    </div>
   </footer>
  <!-- Bootstrap core JavaScript -->
  <script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
  <!-- <script src="https://blog.jwangkun.com/media/scripts/bootstrap.bundle.min.js"></script> -->
  <!-- Bootstrap core JavaScript -->
  <script src="https://cdn.jsdelivr.net/gh/Alanrk/clean-cdn@1.0/scripts/clean-blog.min.js"></script>
  <!-- <script src="https://blog.jwangkun.com/media/scripts/clean-blog.min.js"></script> -->
  <script src="//instant.page/3.0.0" type="module" defer integrity="sha384-OeDn4XE77tdHo8pGtE1apMPmAipjoxUQ++eeJa6EtJCfHlvijigWiJpD7VDPWXV1"></script>
  <style type="text/css">a.back_to_top{text-decoration:none;position:fixed;bottom:40px;right:30px;background:#f0f0f0;height:40px;width:40px;border-radius:50%;line-height:36px;font-size:18px;text-align:center;transition-duration:.5s;transition-propety:background-color;display:none}a.back_to_top span{color:#888}a.back_to_top:hover{cursor:pointer;background:#dfdfdf}a.back_to_top:hover span{color:#555}@media print,screen and(max-width:580px){.back_to_top{display:none!important}}</style>
<a id="back_to_top" href="#" class="back_to_top">
  <span>▲</span></a>
<script>$(document).ready((function(_this) {
    return function() {
      var bt;
      bt = $('#back_to_top');
      if ($(document).width() > 480) {
        $(window).scroll(function() {
          var st;
          st = $(window).scrollTop();
          if (st > 30) {
            return bt.css('display', 'block')
          } else {
            return bt.css('display', 'none')
          }
        });
        return bt.click(function() {
          $('body,html').animate({
            scrollTop: 0
          },
          800);
          return false
        })
      }
    }
  })(this));
  var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?84ab85460bfbe79dbe5776a1df139a8f";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
  </script>
  
<script type="text/javascript" src="https://v1.cnzz.com/z_stat.php?id=1279350888&web_id=1279350888"></script>

  </body>
</html>

